/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.filing;

import com.softwarecraftsmen.orogeny.filing.findFileFilters.FindFilesFilter;
import com.softwarecraftsmen.orogeny.filing.findSubdirectoriesFilters.FindSubdirectoriesFilter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.StringWriter;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Iterator;

public final class AbsoluteDirectoriesAndFiles implements AbsolutePaths, Iterable<AbsolutePath>
{
	@NotNull
	public static AbsoluteDirectoriesAndFiles EmptyAbsoluteDirectoriesAndFiles = absoluteDirectoriesAndFiles(new AbsoluteDirectory[]{});

	private final Set<AbsolutePath> filesAndFolders;

	public AbsoluteDirectoriesAndFiles(final @NotNull Set<AbsolutePath> filesAndFolders)
	{
		this.filesAndFolders = filesAndFolders;
	}

	@NotNull
	public AbsolutePaths and(final @NotNull AbsolutePath absolutePath)
	{
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>(filesAndFolders)
		{{
			add(absolutePath);
		}});
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteDirectory absoluteDirectory)
	{
		return (AbsoluteDirectoriesAndFiles) and((AbsolutePath)absoluteDirectory);
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteFile absoluteFile)
	{
		return (AbsoluteDirectoriesAndFiles) and((AbsolutePath)absoluteFile);
	}

	@NotNull
	public AbsolutePaths and(final @NotNull AbsolutePaths absolutePaths)
	{
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>(filesAndFolders)
		{{
			addAll(absolutePaths.files());
			addAll(absolutePaths.directories());
		}});
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteDirectories absoluteDirectories)
	{
		return (AbsoluteDirectoriesAndFiles) and((AbsolutePaths)absoluteDirectories);
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteFiles absoluteFiles)
	{
		return (AbsoluteDirectoriesAndFiles) and((AbsolutePaths) absoluteFiles);
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteDirectoriesAndFiles absoluteDirectoriesAndFiles)
	{
		return (AbsoluteDirectoriesAndFiles) and((AbsolutePaths)absoluteDirectoriesAndFiles);
	}

	@NotNull
	public String toString()
	{
		return toFileSystemSpecificPath();
	}

	public boolean equals(final @Nullable Object o)
	{
		if (this == o)
		{
			return true;
		}
		if (o == null || getClass() != o.getClass())
		{
			return false;
		}

		final AbsoluteDirectoriesAndFiles that = (AbsoluteDirectoriesAndFiles) o;
		return filesAndFolders.equals(that.filesAndFolders);
	}

	public int hashCode()
	{
		return filesAndFolders.hashCode();
	}

	@SuppressWarnings({"ManualArrayToCollectionCopy"})
	@NotNull
	public static AbsoluteDirectoriesAndFiles absoluteDirectoriesAndFiles(final @NotNull AbsolutePath... filesAndFolders)
	{
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>()
		{{
			for (AbsolutePath filesOrFolder : filesAndFolders)
			{
				add(filesOrFolder);
			}
		}});
	}

	@NotNull
	public static AbsolutePaths absoluteDirectoriesAndFiles(final @NotNull AbsolutePaths... fromDirectoriesAndFiles)
	{
		final AbsolutePaths actual;
		if (fromDirectoriesAndFiles.length == 0)
		{
			actual = EmptyAbsoluteDirectoriesAndFiles;
		}
		else
		{
			AbsolutePaths nearActual = fromDirectoriesAndFiles[0];
			for (int index = 1; index < fromDirectoriesAndFiles.length; index++)
			{
				final AbsolutePaths fromDirectoriesAndFile = fromDirectoriesAndFiles[index];
				nearActual = nearActual.and(fromDirectoriesAndFile);
			}
			actual = nearActual;
		}
		return actual;
	}

	@NotNull
	public String toFileSystemSpecificPath()
	{
		final StringWriter writer = new StringWriter();
		boolean afterFirst = false;
		for (AbsolutePath fileOrFolder : filesAndFolders)
		{
			if (afterFirst)
			{
				writer.write(fileOrFolder.toFileSystemSpecificPathPrefixedByPathSeparator());
			}
			else
			{
				writer.write(fileOrFolder.toFileSystemSpecificPath());
			}
			afterFirst = true;
		}
		return writer.toString();
	}

	public boolean exists()
	{
		for (AbsolutePath fileOrFolder : filesAndFolders)
		{
			if (!fileOrFolder.exists())
			{
				return false;
			}
		}
		return true;
	}

	@NotNull
	public Set<File> toFiles()
	{
		return new LinkedHashSet<File>()
		{{
			for (AbsolutePath filesOrFolder : filesAndFolders)
			{
				add(filesOrFolder.toFile());
			}
		}};
	}

	@NotNull
	public Set<AbsoluteFile> findFiles(final @NotNull FindFilesFilter findFilesFilter)
	{
		return new LinkedHashSet<AbsoluteFile>()
		{{
			for (AbsolutePath filesOrFolder : filesAndFolders)
			{
				addAll(filesOrFolder.findFiles(findFilesFilter));
			}
		}};
	}

	@NotNull
	public Set<AbsoluteDirectory> findSubdirectories(final @NotNull FindSubdirectoriesFilter findSubdirectoriesFilter)
	{
		return new LinkedHashSet<AbsoluteDirectory>()
		{{
			for (AbsolutePath filesOrFolder : filesAndFolders)
			{
				addAll(filesOrFolder.findSubdirectories(findSubdirectoriesFilter));
			}
		}};
	}

	@NotNull
	public Set<AbsoluteFile> files()
	{
		return new LinkedHashSet<AbsoluteFile>()
		{{
			for (AbsolutePath filesAndFolder : filesAndFolders)
			{
				if (filesAndFolder instanceof AbsoluteFile)
				{
					add((AbsoluteFile) filesAndFolder);
				}
			}
		}};
	}

	@NotNull
	public Set<AbsoluteDirectory> directories()
	{
		return new LinkedHashSet<AbsoluteDirectory>()
		{{
			for (AbsolutePath filesAndFolder : filesAndFolders)
			{
				if (filesAndFolder instanceof AbsoluteDirectory)
				{
					add((AbsoluteDirectory) filesAndFolder);
				}
			}
		}};
	}

	public boolean isEmpty()
	{
		return filesAndFolders.isEmpty();
	}

	@NotNull
	public Iterator<AbsolutePath> iterator()
	{
		return filesAndFolders.iterator();
	}
}
